package main

import (
	"database/sql"
	"encoding/base64"
	"errors"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"github.com/spf13/viper"
	"golang.org/x/net/html"
	"log"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"
	"uyh/bookmark/utils"
)

type Link struct {
	Group     string
	Href      string
	Title     string
	Host      string
	Level     int
	CreatedAt int64
	UpdatedAt int64
}

var (
	group = ""
	dsn   = "%s:%s@tcp(%s:%s)/%s"
	links []Link
)

func traverse(n *html.Node, level int) {
	if n.Type == html.ElementNode {
		if n.Data == "h3" {
			group = n.FirstChild.Data
		} else if n.Data == "a" {
			l := Link{
				Group: group,
				Level: level,
				Title: n.FirstChild.Data,
			}
			for _, attr := range n.Attr {
				if attr.Key == "href" {
					l.Href = attr.Val
					pUrl, _ := url.Parse(l.Href)
					if pUrl != nil {
						l.Host = pUrl.Host
					}
				} else if attr.Key == "add_date" {
					ts, _ := strconv.Atoi(attr.Val)
					l.CreatedAt = int64(ts)
				} else if attr.Key == "last_modified" {
					ts, _ := strconv.Atoi(attr.Val)
					l.UpdatedAt = int64(ts)
				} else if attr.Key == "icon" {
					s := strings.TrimPrefix(attr.Val, "data:image/png;base64,")
					imgData, _ := base64.StdEncoding.DecodeString(s)
					if imgData != nil {
						var name string
						if l.Host != "" {
							name = l.Host
						} else {
							name = "__" + utils.RandString(16, 5, "")
						}
						os.WriteFile("static/icons/"+name+".png", imgData, 0644)
					}
				}
			}
			links = append(links, l)
		}
	}
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		traverse(c, level+1)
	}
}

func bookmarkHandler(w http.ResponseWriter, _ *http.Request) {
	s := `<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><title>书签</title></head>`
	s += `<body>`
	s += `<ul>`

	js := `onload="this.style.display='inline-block'"`
	for _, l := range links {
		s += `<li>`
		s += `[` + l.Group + `](` + strconv.Itoa(l.Level) + `)`
		s += `<a href="` + l.Href + `">`
		s += `<img style="display:none;margin:0 5px" ` + js + ` src="./static/icons/` + l.Host + `.png" />` + l.Title + `</a>`
		s += `</li>`
	}
	s += `</ul>`
	s += `</body>`
	s += `</html>`
	fmt.Fprintf(w, s)
}

func tableExists(db *sql.DB, tableName string) (bool, error) {
	query := fmt.Sprintf("SHOW TABLES LIKE '%s'", tableName)
	var name string
	err := db.QueryRow(query).Scan(&name)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return false, nil
		}
		return false, err
	}
	return true, nil
}

// 创建表
func createTable(db *sql.DB) error {
	createTableSQL := `
CREATE TABLE bookmarks
(
    id         INT AUTO_INCREMENT PRIMARY KEY,
    title      VARCHAR(255)  NOT NULL,
    url        VARCHAR(2048) NOT NULL,
    pre        VARCHAR(50)   NOT NULL,
    domain     VARCHAR(50)   NOT NULL,
    created_at INT UNSIGNED  NOT NULL,
    updated_at INT UNSIGNED  NOT NULL
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
`
	_, err := db.Exec(createTableSQL)
	return err
}

type MySQLConfig struct {
	Host     string `json:"host"`
	Port     string `json:"port"`
	User     string `json:"user"`
	Password string `json:"password"`
	DB       string `json:"db"`
}

type Config struct {
	MySQL map[string]MySQLConfig
}

func getBookmarkId(db *sql.DB, sUrl string) int {
	// 执行查询
	var id int
	db.QueryRow("SELECT id FROM bookmarks where url=?", sUrl).Scan(&id)
	return id
}

func main() {

	env := ".local"
	viper.SetConfigName("config" + env)
	viper.SetConfigType("toml")
	viper.AddConfigPath(".")

	err := viper.ReadInConfig() // Find and read the config file
	if err != nil {             // Handle errors reading the config file
		panic(fmt.Errorf("fatal error config file: %w", err))
	}

	var config Config
	if err = viper.Unmarshal(&config); err != nil {
		log.Fatalf("Unable to decode into struct: %v", err)
	}

	mainDB := config.MySQL["main"]

	dsn = fmt.Sprintf(dsn, mainDB.User, mainDB.Password, mainDB.Host, mainDB.Port, mainDB.DB)

	fmt.Println(dsn)
	var db *sql.DB
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		log.Fatalf("Error opening database: %v", err)
	}
	defer db.Close()

	// 验证数据库连接
	if err = db.Ping(); err != nil {
		log.Fatalf("Error pinging database: %v", err)
	}

	var ok bool
	ok, err = tableExists(db, "bookmarks")

	if err != nil {
		log.Fatal(err.Error())
	}

	if !ok {
		err = createTable(db)
		if err != nil {
			log.Fatal("创建书签表异常：\n", err.Error())
		}
	}

	fh, _ := os.Open("./favorites_2024_11_20.html")
	defer fh.Close()
	node, _ := html.Parse(fh)
	traverse(node, 0)

	//ds := []string{
	//	"com.cn",
	//	"*.jp",
	//}

	for _, link := range links {

		id := getBookmarkId(db, link.Href)

		if id > 0 {
			fmt.Println(id, link.Href)
			continue
		}

		sSql := "insert into bookmarks (title,url,pre,domain,created_at,updated_at) values (?,?,?,?,?,?)"

		stmt, err := db.Prepare(sSql)

		if err != nil {
			log.Fatalf("Failed to prepare statement: %v", err)
		}
		defer stmt.Close()
		now := time.Now().Unix()
		u, _ := url.Parse(link.Href)
		pre := ""
		domain := ""
		if u != nil {
			ds := strings.Split(u.Host, ".")
			n := len(ds)

			if n == 2 {
				domain = u.Host
			} else if n > 2 {
				l2 := ds[n-2]
				if len(l2) <= 3 && n >= 3 {
					pre = strings.Join(ds[0:n-3], ".")
					domain = strings.Join(ds[n-3:], ".")
				} else {
					pre = strings.Join(ds[0:n-2], ".")
					domain = strings.Join(ds[n-2:], ".")
				}
			}
		}
		_, err = stmt.Exec(link.Title, link.Href, pre, domain, now, now)
		if err != nil {
			log.Printf("Error executing query: %v", err)
		}
	}

	//bLinks, _ := json.MarshalIndent(links, "", "  ")
	//os.WriteFile("./bookmark.json", bLinks, 0644)
	//
	//// 设置静态文件目录
	//fs := http.FileServer(http.Dir("./static"))
	//
	//// 将静态文件处理器挂载到 "/static/" 路径
	//http.Handle("/static/", http.StripPrefix("/static/", fs))
	//
	//http.HandleFunc("/", bookmarkHandler)
	//
	//// 启动web服务，监听9090端口
	//err = http.ListenAndServe(":9090", nil)
	//if err != nil {
	//	log.Fatal("ListenAndServe: ", err)
	//}
}
